/*
 * Copyright (c) 2024 Black Sesame Technologies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <unordered_map>
#include "dal/allocator.h"
#include "dal_rticonnextdds/utils/ReferenceCount.hpp"
#include "dal_rticonnextdds/converter/StatusConverter.hpp"
#include "dal_rticonnextdds/converter/StatusMaskConverter.hpp"
#include "dal_rticonnextdds/converter/QosConverter.hpp"
#include "dal_rticonnextdds/converter/RetTypeConverter.hpp"
#include "dal_rticonnextdds/listener_impl/TopicListenerImpl.hpp"
#include "dal_rticonnextdds/utils/log.hpp"
#include "dal_rticonnextdds/utils/EntityGetter.hpp"
#include "dal_rticonnextdds/type_support/RtiTypeSupportImpl.hpp"
#include "dal_rticonnextdds/utils/EntityMappingManager.hpp"
#include "dal_utils/StrHelper.hpp"
#include "dal_utils/MsgLibLoader.hpp"

#include "dal_connextdds/dds_api.hpp"

dal_type_support_t* dal_create_type_support(void* type_support)
{
    if (!type_support) {
        LOG_ERROR() << "TypeSupport is null. " << LOG_ENDL;
        return nullptr;
    }

    // fully qualified name
    const char* fqname = static_cast<const char*>(type_support);
    if (!MsgLibLoader::loadLibrary(fqname)) {
        LOG_ERROR() << "Load msg lib failed." << LOG_ENDL;
        return nullptr;
    }

    void* func = MsgLibLoader::getFunction(fqname);
    if (!func) {
        LOG_ERROR() << "Get function failed." << LOG_ENDL;
        return nullptr;
    }

    const dalidl_message_type_support_t* ts_handles =
        reinterpret_cast<const dalidl_message_type_support_t* (*)()>(func)();
    if (!ts_handles) {
        LOG_ERROR() << "Get message type supports failed." << LOG_ENDL;
        return nullptr;
    }

    DAL_Connext_MessageTypeSupport* cxtMsgTs = new DAL_Connext_MessageTypeSupport(
        DAL_CONNEXT_MESSAGE_USERDATA, ts_handles, nullptr, nullptr);

    RtiTypeSupportImpl* tsImpl = new RtiTypeSupportImpl();
    tsImpl->tsHandles_ = ts_handles;
    tsImpl->cxtMsgTs_ = cxtMsgTs;

    auto ts = dal_type_support_allocate();
    ts->handle_ = tsImpl;
    ts->type_name_ = dal_strdup(cxtMsgTs->type_name());

    MsgLibLoader::typeSupportMap_[ts->type_name_] = tsImpl;

    return ts;
}

dal_ret_t dal_delete_type_support(dal_type_support_t* ts)
{
    if (ts == nullptr || ts->handle_ == nullptr)
        return DAL_RETCODE_BAD_PARAMETER;

    RtiTypeSupportImpl* tsImpl = reinterpret_cast<RtiTypeSupportImpl*>(ts->handle_);
    if (tsImpl->cxtMsgTs_) {
        delete tsImpl->cxtMsgTs_;
        tsImpl->cxtMsgTs_ = nullptr;
    }
    dal_strfree(ts->type_name_);
    dal_type_support_free(ts);
    reinterpret_cast<dal_type_support_t*>(ts)->handle_ = nullptr;
    return DAL_RETCODE_OK;
}

const char* dal_type_support_get_type_name(dal_type_support_t* ts)
{
    if (ts == nullptr || ts->handle_ == nullptr) {
        LOG_ERROR() << "TypeSupport is null." << LOG_ENDL;
        return nullptr;
    }

    RtiTypeSupportImpl* tsImpl = reinterpret_cast<RtiTypeSupportImpl*>(ts->handle_);
    return tsImpl->cxtMsgTs_->type_name();
}

dal_ret_t dal_type_support_register_type(dal_type_support_t* ts,
                                         dal_domain_participant_t* participant,
                                         const char* type_name)
{
    if (!ts || !ts->handle_) {
        LOG_ERROR_WITH_LOCATION() << "TypeSupport is null." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    if (!type_name || strlen(type_name) == 0) {
        LOG_ERROR_WITH_LOCATION() << "Type name is null." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    DDS_DomainParticipant* r_participant = get_rtidds_domain_participant(participant);
    if (!r_participant) {
        LOG_ERROR_WITH_LOCATION() << "get rtidds participant failed." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    RtiTypeSupportImpl* tsImpl = reinterpret_cast<RtiTypeSupportImpl*>(ts->handle_);
    if (!tsImpl->tsHandles_) {
        LOG_ERROR() << "TypeSupport handles is null." << LOG_ENDL;
        return DAL_RETCODE_ERROR;
    }

    bool ret = RegisterType(r_participant, tsImpl->tsHandles_);

    return ret ? DAL_RETCODE_OK : DAL_RETCODE_ERROR;
}

dal_topic_listener_t* dal_create_topic_listener(dal_topic_listener_callbacks_t callbacks)
{
    auto listener = dal_topic_listener_allocate();
    if (listener == nullptr) {
        return nullptr;
    }
    listener->handle_ = new TopicListenerImpl(callbacks);
    if (!listener->handle_)
        LOG_ERROR() << "TopicListenerImpl memory allocation failed." << LOG_ENDL;
    return listener;
}

dal_ret_t dal_delete_topic_listener(dal_topic_listener_t* listener)
{
    if (!listener || !listener->handle_) {
        LOG_ERROR_WITH_LOCATION() << "listener is invalid." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    auto r_listener_impl = reinterpret_cast<TopicListenerImpl*>(listener->handle_);
    if (r_listener_impl->ref_count_.Get() > 0) {
        LOG_ERROR() << "can't delete topic listener because it's entity still exist." << LOG_ENDL;
        return DAL_RETCODE_PRECONDITION_NOT_MET;
    }

    delete r_listener_impl;
    dal_topic_listener_free(listener);
    reinterpret_cast<dal_topic_listener_t*>(listener)->handle_ = nullptr;

    return DAL_RETCODE_OK;
}

dal_topic_t* dal_domain_participant_create_topic(dal_domain_participant_t* participant,
                                                 const char* topic_name,
                                                 const char* type_name,
                                                 dal_topic_qos_t* qos,
                                                 dal_topic_listener_t* listener,
                                                 dal_status_mask_t mask)
{
    dal_ret_t d_ret = DAL_RETCODE_OK;

    if (!topic_name || !type_name || strlen(topic_name) == 0 || strlen(type_name) == 0) {
        LOG_ERROR_WITH_LOCATION() << "topic or type name is invalid parameter." << LOG_ENDL;
        return nullptr;
    }

    DDS_DomainParticipant* r_participant = get_rtidds_domain_participant(participant);
    if (!r_participant) {
        LOG_ERROR_WITH_LOCATION() << "get rtidds participant failed." << LOG_ENDL;
        return nullptr;
    }

    auto tsImpl = MsgLibLoader::typeSupportMap_[type_name];
    if (!tsImpl) {
        LOG_ERROR_WITH_LOCATION() << "type support is null." << LOG_ENDL;
        return nullptr;
    }

    auto d_topic = dal_topic_allocate();
    if (!d_topic) {
        LOG_ERROR_WITH_LOCATION() << "dal topic allocate failed." << LOG_ENDL;
        return nullptr;
    }

    d_topic->type_support_impl_ = tsImpl;

    DDS_TopicQos* r_qos = new DDS_TopicQos();
    DDS_DomainParticipant_get_default_topic_qos(r_participant, r_qos);
    d_ret = QosConverter::GetInstance()->ConvertTopicQosFromDalToRtidds(qos, r_qos);
    if (DAL_RETCODE_OK != d_ret) {
        LOG_ERROR_WITH_LOCATION() << "convert topic qos failed." << LOG_ENDL;
        dal_topic_free(d_topic);
        return nullptr;
    }

    DDS_StatusMask r_mask = DDS_STATUS_MASK_NONE;
    d_ret = StatusMaskConverter::GetInstance()->ConvertStatusMaskFromDalToRtidds(mask, &r_mask);
    if (DAL_RETCODE_OK != d_ret) {
        LOG_ERROR_WITH_LOCATION() << "convert status mask failed." << LOG_ENDL;
        dal_topic_free(d_topic);
        return nullptr;
    }

    TopicListenerImpl* r_listener_impl =
        listener ? reinterpret_cast<TopicListenerImpl*>(listener->handle_) : nullptr;

    DDS_Topic* r_topic =
        DDS_DomainParticipant_create_topic(r_participant,
                                           topic_name,
                                           type_name,
                                           r_qos,
                                           r_listener_impl ? r_listener_impl->getListener() : NULL,
                                           r_mask);

    delete r_qos;
    if (!r_topic) {
        LOG_ERROR_WITH_LOCATION() << "create topic failed." << LOG_ENDL;
        dal_topic_free(d_topic);
        return nullptr;
    }

    EntityMappingAddTopic(r_topic, d_topic);
    d_topic->handle_ = new DalTopicImpl(r_topic, r_listener_impl);
    return d_topic;
}

dal_topic_t* dal_domain_participant_create_topic_with_profile(dal_domain_participant_t* participant,
                                                              const char* topic_name,
                                                              const char* type_name,
                                                              dal_topic_qos_t* qos,
                                                              const char* profile_name,
                                                              dal_topic_listener_t* listener,
                                                              dal_status_mask_t mask)

{
    if (!profile_name || strlen(profile_name) == 0) {
        return dal_domain_participant_create_topic(
            participant, topic_name, type_name, qos, listener, mask);
    }
    dal_ret_t d_ret = DAL_RETCODE_OK;

    if (!topic_name || !type_name || strlen(topic_name) == 0 || strlen(type_name) == 0) {
        LOG_ERROR_WITH_LOCATION() << "topic or type name is invalid parameter." << LOG_ENDL;
        return nullptr;
    }

    DDS_DomainParticipant* r_participant = get_rtidds_domain_participant(participant);
    if (!r_participant) {
        LOG_ERROR_WITH_LOCATION() << "get rtidds participant failed." << LOG_ENDL;
        return nullptr;
    }

    auto tsImpl = MsgLibLoader::typeSupportMap_[type_name];
    if (!tsImpl) {
        LOG_ERROR_WITH_LOCATION() << "type support is null." << LOG_ENDL;
        return nullptr;
    }

    auto d_topic = dal_topic_allocate();
    if (!d_topic) {
        LOG_ERROR_WITH_LOCATION() << "dal topic allocate failed." << LOG_ENDL;
        return nullptr;
    }

    d_topic->type_support_impl_ = tsImpl;

    DDS_DomainParticipantFactory* factory = DDS_DomainParticipantFactory_get_instance();
    DDS_TopicQos* r_qos = new DDS_TopicQos();
    DDS_ReturnCode_t r_ret = DDS_DomainParticipantFactory_get_topic_qos_from_profile(
        factory, r_qos, "dal_rticonnext_qos_profile_lib", profile_name);
    if (r_ret != DDS_RETCODE_OK) {
        d_ret = QosConverter::GetInstance()->ConvertTopicQosFromDalToRtidds(qos, r_qos);
        if (DAL_RETCODE_OK != d_ret) {
            LOG_ERROR_WITH_LOCATION() << "convert topic qos failed." << LOG_ENDL;
            dal_topic_free(d_topic);
            return nullptr;
        }
    }

    DDS_StatusMask r_mask = DDS_STATUS_MASK_NONE;
    d_ret = StatusMaskConverter::GetInstance()->ConvertStatusMaskFromDalToRtidds(mask, &r_mask);
    if (DAL_RETCODE_OK != d_ret) {
        LOG_ERROR_WITH_LOCATION() << "convert status mask failed." << LOG_ENDL;
        dal_topic_free(d_topic);
        return nullptr;
    }

    TopicListenerImpl* r_listener_impl =
        listener ? reinterpret_cast<TopicListenerImpl*>(listener->handle_) : nullptr;

    DDS_Topic* r_topic =
        DDS_DomainParticipant_create_topic(r_participant,
                                           topic_name,
                                           type_name,
                                           r_qos,
                                           r_listener_impl ? r_listener_impl->getListener() : NULL,
                                           r_mask);

    delete r_qos;
    if (!r_topic) {
        LOG_ERROR_WITH_LOCATION() << "create topic failed." << LOG_ENDL;
        dal_topic_free(d_topic);
        return nullptr;
    }

    EntityMappingAddTopic(r_topic, d_topic);
    d_topic->handle_ = new DalTopicImpl(r_topic, r_listener_impl);
    return d_topic;
}

dal_ret_t dal_domain_participant_delete_topic(dal_domain_participant_t* participant,
                                              dal_topic_t* topic)
{
    DDS_DomainParticipant* r_participant = get_rtidds_domain_participant(participant);
    if (!r_participant) {
        LOG_ERROR_WITH_LOCATION() << "get rtidds participant failed." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    DDS_Topic* r_topic = get_rtidds_topic(topic);
    if (!r_topic) {
        LOG_ERROR_WITH_LOCATION() << "rtidds topic is invalid." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    DDS_ReturnCode_t ret = DDS_DomainParticipant_delete_topic(r_participant, r_topic);
    if (ret != DDS_RETCODE_OK) {
        LOG_ERROR_WITH_LOCATION() << "delete topic failed." << LOG_ENDL;
        return DAL_RETCODE_ERROR;
    }

    EntityMappingRemoveTopic(r_topic);

    auto topic_impl = reinterpret_cast<DalTopicImpl*>(topic->handle_);
    delete topic_impl;
    dal_topic_free(topic);
    reinterpret_cast<dal_topic_t*>(topic)->handle_ = nullptr;

    return DAL_RETCODE_OK;
}